﻿using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using System.Text;

namespace BES
{

    public class GDAT<T>
    {

        private List<String> ColNames = new List<String>();
        private DataTable Table;

        public GDAT() : this(typeof(T).Name) { }

        public GDAT(string overrideName)
        {
            var name = typeof(T).Name;
            var fields = typeof(T).GetFields();
            Table = new DataTable(overrideName);
            foreach ( var field in fields )
            {
                var ftype = field.FieldType;
                var fname = field.Name;
                Table.Columns.Add(fname, ftype);
            }
            if ( Table.Columns.Count == 0 )
                throw new DataException("Cannot support supplied class structure. No applicable elements for data management.");
        }

        public string Name { get { return Table.TableName; } }

        public int Count { get { return Table.Rows.Count; } }

        public void Insert(T t)
        {
            var row = Table.NewRow();
            Populate(row, t);
            Table.Rows.Add(row);
            Table.AcceptChanges();
        }

        public int Update(Func<T, bool> query, Func<T, T> update)
        {
            var results = Remove(query);
            foreach ( var item in results )
            {
                var newItem = update.Invoke(item);
                Insert(newItem);
            }
            Table.AcceptChanges();
            return results.Count();
        }

        public List<T> Select(Func<T, bool> query)
        {
            var results = from row in Table.AsEnumerable() let t = Mutate(row) where query.Invoke(t) select t;
            return results.ToList();
        }

        public List<T> Remove(Func<T, bool> query)
        {
            var list = Select(query);
            var rows = ( from r in Table.AsEnumerable() where query.Invoke(Mutate(r)) select r ).ToList();
            foreach ( var row in rows )
                Table.Rows.Remove(row);
            Table.AcceptChanges();
            return list;
        }

        public void Clear()
        {
            Table.Clear();
            Table.AcceptChanges();
        }

        public void Save(string file)
        {
            if ( Count == 0 )
            {
                if ( File.Exists(file) )
                    File.Delete(file);
                return;
            }
            Table.WriteXml(file);
        }

        public void Load(string file)
        {
            Clear();
            if ( !File.Exists(file) )
                return;
            Table.ReadXml(file);
            Table.AcceptChanges();
        }

        private static void Populate(DataRow row, T t)
        {
            row.BeginEdit();
            var fields = typeof(T).GetFields();
            foreach ( var field in fields )
            {
                var ftype = field.DeclaringType;
                if ( ftype.IsInstanceOfType(typeof(List<>)) )
                    continue;
                var value = field.GetValue(t);
                row.SetField(field.Name, value);
            }
            row.EndEdit();
        }

        private static T Mutate(DataRow row)
        {
            var type = typeof(T);
            var obj = (T)type.GetConstructor(new Type[] { }).Invoke(new object[] { });
            var fields = type.GetFields();
            foreach ( var field in fields )
            {
                var ftype = field.DeclaringType;
                if ( ftype.IsInstanceOfType(typeof(List<>)) )
                    continue;
                var value = row.Field<object>(field.Name);
                type.GetField(field.Name).SetValue(obj, value);
            }
            return obj;
        }

        public void OutputToConsole()
        {
            var str = new StringBuilder();
            str.Append("TABLE ");
            str.Append(Table.TableName);
            foreach ( var row in Table.AsEnumerable().ToList() )
            {
                str.Append("\n   ");
                str.Append(Mutate(row).ToString());
            }
            str.Append("\n");
            Console.WriteLine(str);
        }
    }

    public enum DatatTypeDefinitions : byte
    {
        PublicMembers = 0x01,
        ProtectedMembers = 0x02,
        PublicAccessors = 0x04,
        ProtecetdAccessors = 0x08
    }
}
